package com.lipaluma.field.mapper.single.impl;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.lipaluma.annotations.mapping.MapColumn;
import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.FieldMergeable;
import com.lipaluma.field.mapper.initializer.FieldMapperInitializer;
import com.lipaluma.field.mapper.initializer.XmlFieldMapperInitializer;
import com.lipaluma.field.mapper.mapping.MappingInfo;
import com.lipaluma.field.merge.FieldMergedFunction;
import com.lipaluma.io.Row;
import com.lipaluma.parser.marshalling.ObjectMarshalled;
import com.lipaluma.util.ReflectionUtils;
import com.lipaluma.xml.mapping.XmlFieldMapping;

public class OneToManyFieldMapper implements FieldMapper, FieldMapperInitializer, XmlFieldMapperInitializer, FieldMergeable {
	private Field field;
	private FieldMapper fieldMapper;
	private FieldMergedFunction fieldMergedFunction;
	
	public OneToManyFieldMapper() {
		throw new CsvParserException("To use 'one to many', use :\n  - by annotations : @OneToMany\n  - by xml : use attribute oneToMany\n  - by java builder : use the specific method");
	}
	public OneToManyFieldMapper(FieldMapper fieldMapper) {
		this.fieldMapper = fieldMapper;
	}
	
	@Override
	public void init(Context context, Field field) {
		if(!Collection.class.isAssignableFrom(field.getType()))
			throw new CsvParserException("Error on field "+field.getName()+" : The type of the field "+this.getClass().getSimpleName()+" must be a collection type.");
		this.field = field;
		if(FieldMapperInitializer.class.isAssignableFrom(fieldMapper.getClass()))
			((FieldMapperInitializer)fieldMapper).init(context, field);
	}

	@Override
	public void init(Context context, Field field, XmlFieldMapping mapping) {
		if(!Collection.class.isAssignableFrom(field.getType()))
			throw new CsvParserException(MapColumn.class+" declared "+this.getClass().getSimpleName()+" must be a collection type.");
		this.field = field;
		if(XmlFieldMapperInitializer.class.isAssignableFrom(fieldMapper.getClass()))
			((XmlFieldMapperInitializer)fieldMapper).init(context, field, mapping);
	}

	@Override
	public Object unmarshal(Row row) {
		Collection<Object> collection = ReflectionUtils.instantiateCollection(field.getType());
		collection.add(fieldMapper.unmarshal(row));
		return collection;
	}

	@Override
	public void marshalValue(Object value, ObjectMarshalled marshal) {
		Collection<Object> collection = (Collection<Object>)value;
		if(collection != null && !collection.isEmpty()) {
			List<ObjectMarshalled> valuesMarshalledOfOneToManyField = new ArrayList<ObjectMarshalled>();
			for(Object valueToMarshal : collection) {
				ObjectMarshalled valueMarshalled = new ObjectMarshalled();
				fieldMapper.marshalValue(valueToMarshal, valueMarshalled);
				valuesMarshalledOfOneToManyField.add(valueMarshalled);
			}
			marshal.addObjectMarshalledValuesForField(valuesMarshalledOfOneToManyField);
		}
	}

	public String getDescription() {
		return "[MapColumn "+this.getClass()+": (field:"+field.getName()+")]";
	}

	@Override
	public Field getField() {
		return field;
	}

	@Override
	public MappingInfo[] getMappings() {
		return fieldMapper.getMappings();
	}

	@Override
	public boolean hasToBeMerged() {
		return fieldMergedFunction != null;
	}

	@Override
	public FieldMergedFunction mergedBy() {
		return fieldMergedFunction;
	}

	@Override
	public void mergeWith(FieldMergedFunction fieldMergedFunction) {
		this.fieldMergedFunction = fieldMergedFunction;
	}
	
	public FieldMapper getFieldMapper() {
		return fieldMapper;
	}
}
